Repository Public
1import type { Metadata } from "next";2import { PageIntro } from "@/components/ui/Section";3import { research } from "@/lib/data";4import { formatDate, getDict, type Locale } from "@/lib/i18n";5import { pageMetadata } from "@/lib/seo";67type Props = { params: Promise<{ locale: Locale }> };89export async function generateMetadata({ params }: Props): Promise<Metadata> {10 return pageMetadata((await params).locale, "research", "/research");11}1213const TONE = { completed: "text-ok border-ok/30 bg-ok/10", ongoing: "text-warn border-warn/30 bg-warn/10", proposed: "text-accent border-accent/30 bg-accent-soft" } as const;1415export default async function ResearchPage({ params }: Props) {16 const { locale } = await params;17 const t = getDict(locale);18 const r = t.research;19 const groups = (["completed", "ongoing", "proposed"] as const).map((status) => ({ status, items: research.filter((e) => e.status === status) })).filter((g) => g.items.length > 0);20 return (21 <>22 <PageIntro eyebrow={r.eyebrow} title={r.title} intro={r.intro} />23 <div className="shell">24 <nav aria-label={r.eyebrow} className="flex flex-wrap gap-2 border-y border-line py-5">25 {groups.map((g) => (26 <a key={g.status} href={`#${g.status}`} className={`rounded-full border px-3.5 py-1.5 font-mono text-[0.6875rem] uppercase tracking-[0.1em] ${TONE[g.status]}`}>{t.common.status[g.status]} · {g.items.length}</a>27 ))}28 </nav>29 {groups.map((g) => (30 <section key={g.status} id={g.status} aria-labelledby={`${g.status}-h`} className="scroll-mt-24 pt-16 md:pt-24">31 <h2 id={`${g.status}-h`} className="h2">{t.common.status[g.status]}</h2>32 <ol className="mt-8 divide-y divide-line border-y border-line">33 {g.items.map((e) => (34 <li key={e.id} className="reveal grid gap-5 py-10 md:grid-cols-12 md:gap-8">35 <div className="md:col-span-3">36 <p className="label">{t.common.kinds[e.kind]}</p>37 <p className="num mt-2 font-mono text-[0.8125rem] text-body"><time dateTime={e.date}>{formatDate(e.date, locale)}</time></p>38 <ul className="mt-4 flex flex-wrap gap-1.5">39 {e.themes.map((th) => <li key={th} className="rounded-full border border-line px-2.5 py-0.5 text-[0.75rem] text-mute">{t.common.themes[th as keyof typeof t.common.themes] ?? th}</li>)}40 </ul>41 </div>42 <article className="md:col-span-9">43 <h3 className="max-w-[46ch] text-[1.375rem] font-medium leading-snug tracking-[-0.02em]" lang={e.reference ? "en" : undefined}>{e.reference ? e.title.en : e.title[locale]}</h3>44 {e.reference && locale === "fr" ? <p className="mt-2 max-w-[60ch] text-[0.9375rem] italic text-mute">{e.title.fr}</p> : null}45 <p className="prose-tech mt-5">{e.summary[locale]}</p>46 <div className="mt-6 grid gap-6 border-t border-line pt-5 sm:grid-cols-2">47 <div>48 <h4 className="label">{r.clusterUse}</h4>49 <p className="mt-2 text-[0.9375rem] leading-relaxed text-body">{e.clusterUse[locale]}</p>50 </div>51 <div>52 <h4 className="label">{r.reference}</h4>53 {e.reference ? (54 <p className="mt-2 text-[0.9375rem] leading-relaxed text-body">55 {e.reference.authors} ({e.reference.year}). <cite className="not-italic" lang="en">{e.title.en}</cite>. {e.reference.series}, {e.reference.label}.{" "}56 <a className="link whitespace-nowrap" href={e.reference.url} rel="noopener">{r.read}</a>57 </p>58 ) : (59 <p className="mt-2 text-[0.9375rem] leading-relaxed text-mute">{r.noReference}</p>60 )}61 </div>62 </div>63 </article>64 </li>65 ))}66 </ol>67 </section>68 ))}6970 <section aria-labelledby="stack-h" className="grid gap-8 pt-20 md:grid-cols-12 md:pt-28">71 <div className="md:col-span-3"><h2 id="stack-h" className="eyebrow">{r.stack.title}</h2></div>72 <div className="md:col-span-9">73 <p className="prose-tech">{r.stack.intro}</p>74 <dl className="mt-6 divide-y divide-line border-y border-line">75 {r.stack.items.map((it) => (76 <div key={it.k} className="grid gap-2 py-4 sm:grid-cols-[12rem_1fr] sm:gap-6">77 <dt className="label">{it.k}</dt>78 <dd className="text-[0.9375rem] leading-relaxed text-body">{it.v}</dd>79 </div>80 ))}81 </dl>82 </div>83 </section>84 <section aria-labelledby="own-h" className="grid gap-8 pt-16 md:grid-cols-12 md:pt-20">85 <div className="md:col-span-3"><h2 id="own-h" className="eyebrow">{r.ownership.title}</h2></div>86 <p className="prose-tech md:col-span-9">{r.ownership.body}</p>87 </section>88 </div>89 </>90 );91}92